#pragma once
#include <algorithm>
#include <iostream>
#include <string>
#include <list>
#include <utility>

#include <cstring>

#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "template_string.hpp"
#include "my_log.hpp"
#include "types and marcos.hpp"

template<template_string s = "">
class socket_cpp
{
public:
    socket_cpp(size_t port)
        : _port(port)
    {}

    virtual int s_create() = 0;

    virtual char* s_receive(int fd, char* buffer, int flags, struct sockaddr_in& src_addr, socklen_t& addrlen) = 0;

    virtual ssize_t s_send(int fd, const char* buf, size_t len, int flags, struct sockaddr_in& dest_addr, socklen_t addrlen) = 0;
protected:
    size_t _port;
};

template<>
class socket_cpp<"tcp"> : public socket_cpp<>
{
public:
    socket_cpp(size_t port, size_t max_byte)
        : socket_cpp<>::socket_cpp(port)
    {}

    virtual int s_create() override
    {
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd == -1)
        {
            lg.basic_print(level::FATAL, std::make_tuple("connot create socket fd "), true, true);
            std::terminate();
        }
        struct sockaddr_in _server_info;
        memset(&_server_info, 0, sizeof(struct sockaddr_in));
        _server_info.sin_family = AF_INET;
        _server_info.sin_addr.s_addr = INADDR_ANY;
        _server_info.sin_port = htons(_port);
        bind(fd, reinterpret_cast<sockaddr*>(&_server_info), static_cast<socklen_t>(sizeof(_server_info)));
        if (fd == -1)
        {
            lg.basic_print(level::FATAL, std::make_tuple("connot bind socket fd "), true, true);
            std::terminate();
        }
        return fd;
    }

    int s_listen(int main_fd)
    {
        return listen(main_fd, 20);
    }

    virtual char* s_receive(int fd, char* buffer, int flags, struct sockaddr_in& src_addr, socklen_t& addrlen) override
    {
        ssize_t ret = recvfrom(fd, buffer, MAX_BLOCK_SIZE, flags, reinterpret_cast<sockaddr*>(&src_addr), &addrlen);
        if (ret == -1)
        {
            lg.basic_print(level::WARNING, std::make_tuple("receive error"), true, true);
        }
        return buffer;
    }

    virtual ssize_t s_send(int fd, const char* buffer, size_t len, int flags, struct sockaddr_in& dest_addr, socklen_t addrlen) override
    {
        ssize_t ret = sendto(fd, buffer, len, flags, reinterpret_cast<sockaddr*>(&dest_addr), addrlen);
        if (ret == -1)
        {
            lg.basic_print(level::WARNING, std::make_tuple("end error"), true, true);
        }
        return ret;
    }
};

template<>
class socket_cpp<"udp"> : public socket_cpp<>
{
public:
    socket_cpp(size_t port, size_t max_byte)
        : socket_cpp<>::socket_cpp(port)
    {}

    virtual int s_create() override
    {
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd == -1)
        {
            lg.basic_print(level::FATAL, std::make_tuple("connot create socket fd "), true, true);
            std::terminate();
        }
        struct sockaddr_in _server_info;
        memset(&_server_info, 0, sizeof(struct sockaddr_in));
        _server_info.sin_family = AF_INET;
        _server_info.sin_addr.s_addr = INADDR_ANY;
        _server_info.sin_port = htons(_port);
        bind(fd, reinterpret_cast<sockaddr*>(&_server_info), static_cast<socklen_t>(sizeof(_server_info)));
        if (fd == -1)
        {
            lg.basic_print(level::FATAL, std::make_tuple("connot bind socket fd "), true, true);
            std::terminate();
        }
        return fd;
    }

    virtual char* s_receive(int fd, char* buffer, int flags, struct sockaddr_in& src_addr, socklen_t& addrlen) override
    {
        ssize_t ret = recvfrom(fd, buffer, MAX_BLOCK_SIZE, flags, reinterpret_cast<sockaddr*>(&src_addr), &addrlen);
        if (ret == -1)
        {
            lg.basic_print(level::WARNING, std::make_tuple("receive error"), true, true);
        }
        return buffer;
    }

    virtual ssize_t s_send(int fd, const char* buffer, size_t len, int flags, struct sockaddr_in& dest_addr, socklen_t addrlen) override
    {
        ssize_t ret = sendto(fd, buffer, len, flags, reinterpret_cast<sockaddr*>(&dest_addr), addrlen);
        if (ret == -1)
        {
            lg.basic_print(level::WARNING, std::make_tuple("send error"), true, true);
        }
        return ret;
    }
};